package ekforth

import java.io.Closeable
import java.io.IOException
import java.lang.ClassCastException
import java.net.ServerSocket
import java.util.*
import kotlin.concurrent.thread

class StackUnderflowException : Exception()

class MemoryCell(
    val getter: () -> Any?,
    val setter: (Any?) -> Unit
)

class Stack {
    val stack: MutableList<Any?> = mutableListOf()

    fun push(obj: Any?) {
        stack.add(obj)
    }

    fun pop(): Any? = if (stack.isEmpty()) {
        throw StackUnderflowException()
    } else {
        stack.removeAt(stack.size - 1)
    }

    fun peek() = if (stack.isEmpty()) {
        throw StackUnderflowException()
    } else {
        stack.last()
    }

    fun clear() {
        stack.clear()
    }

    fun roll(u: Int) {
        if (stack.size < u + 1) error("Stack underflow")
        val tail = stack.takeLast(u + 1).toMutableList()
        Collections.rotate(tail, -1)
        repeat(u + 1) { stack.removeLast() }
        stack.addAll(tail)
    }
}

class Dictionary {
    val entries: MutableList<Any?> = mutableListOf()
    operator fun get(i: Int) = entries[i]
    operator fun set(i: Int, value: Any?) {
        entries[i] = value
    }
    fun add(obj: Any?): Int {
        entries.add(obj)
        return here() - 1
    }
    fun here(): Int = entries.size
    fun dropLast(n: Int) {
        entries.dropLast(n)
    }

    override fun toString() = entries.withIndex().map { "${it.index}: ${it.value}" }.joinToString("\n") { it }

}

private sealed class Instruction {
    abstract fun perform(forth: EKForth): String?
    class FunCall(val function: EKForth.() -> Unit) : Instruction() {
        override fun perform(forth: EKForth): String? {
            ++ forth.eip
            try {
                function.invoke(forth)
                return null
            } catch(e: Exception) {
                return e.toString()
            }
        }
        override fun toString(): String = "FUNCALL"
    }

    class Jump(val address: Int) : Instruction() {
        override fun perform(forth: EKForth): String? {
            if (address < forth.dictionary.here()) {
                forth.eip = address
                return null
            } else {
                return("Invalid address")
            }
        }
        override fun toString(): String = "JUMP $address"
    }

    object End : Instruction() {
        override fun perform(forth: EKForth): String? {
            forth.end = true
            return null
        }
        override fun toString(): String = "END"
    }

}

sealed class Input {

    companion object {
        private val eof = ForthWord("")
        fun accept(string: String) = StringInput(string)
        fun accept(input: Iterator<Any?>) = ParsedInput(input)
        fun accept(input: List<Any?>) = ParsedInput(input.iterator())
    }

    abstract fun word(): Any?
    abstract fun word(delimiter: Char): Any?

    class StringInput(val string: String) : Input() {
        var start = 0
        val strippedComment = string.replace("\\\\.*".toRegex(), "")
        override fun word(): Any? {
            while (start < strippedComment.length && strippedComment[start].isWhitespace()) ++start
            if (start == strippedComment.length) return eof
            val tokenStart = start
            while (start < strippedComment.length && !strippedComment[start].isWhitespace()) ++start
            val token = strippedComment.substring(tokenStart, start)
            while (start < strippedComment.length && strippedComment[start].isWhitespace()) ++start
            return token.toIntOrNull() ?: token.toDoubleOrNull() ?: ForthWord(token)
        }

        override fun word(delimiter: Char): Any? {
            while (start < strippedComment.length && strippedComment[start] == delimiter) ++start
            if (start == strippedComment.length) return eof
            val tokenStart = start
            while (start < strippedComment.length && !(strippedComment[start] == delimiter)) ++start
            val token = strippedComment.substring(tokenStart, start)
            if (start < strippedComment.length && strippedComment[start] == delimiter) ++start
            return token.toIntOrNull() ?: token.toDoubleOrNull() ?: ForthWord(token)
        }
    }

    class ParsedInput(val input: Iterator<Any?>) : Input() {
        override fun word(): Any? = if (input.hasNext()) input.next() else eof
        override fun word(delimiter: Char): Any? = word()
    }
}

@Suppress("UNCHECKED_CAST")
class EKForth {

    var lastDefinition: Int = -1
    val dictionary = Dictionary()
    val stack = Stack()
    val returnStack = Stack()
    var ip = 0
    var w: Int = 0
    var eip = -1
    var end = true
    var pfa = -1

    data class Pointers(
        val eip: Int,
        val ip: Int,
        val w: Int,
        val pfa: Int
    )

    var input: Input = Input.accept("")

    fun accept(input: Input) {
        this.input = input
        run()
    }

    fun accept(input: String) {
        this.input = Input.accept(input)
        run()
    }
    fun accept(input: List<Any?>) {
        this.input = Input.accept(input)
        run()
    }
    fun accept(input: Iterator<Any?>) {
        this.input = Input.accept(input)
        run()
    }

    val next = dictionary.here().also {
        dictionary.add(Instruction.FunCall {
            w = try { dictionary[ip] as Int } catch (e: ClassCastException) { println("ip: $ip dictionary[ip]: ${dictionary[ip]} eip: $eip "); throw e }
            ++ ip
            pfa = w + 1
            eip = w // jump to w
        } )
    }

    private val docreate = dictionary.here().also {
        dictionary.add(Instruction.FunCall {
            stack.push(w + 1)
            eip = next
        })
    }

    private val doconst = dictionary.here().also {
        dictionary.add(Instruction.FunCall {
            stack.push(dictionary[w + 1])
            eip = next
        })
    }

    private val dovar = docreate

    val allot = defWord("ALLOT") {
        val n = stack.pop() as Int
        when {
            n > 0 -> repeat(stack.pop() as Int) { comma(0) }
            n < 0 -> dictionary.dropLast(- n)
        }
    }

    val state = createHeader("STATE").also {
        comma(Instruction.Jump(dovar))
        comma(0)
    }.also { lastDefinition = it }

    val variable = defWord("VARIABLE") {
            lastDefinition = createHeader(((kWord() as ForthWord).word))
            comma(Instruction.Jump(dovar))
            comma(0)
    }

    val constant = defWord("CONSTANT") {
            lastDefinition = createHeader(((kWord() as ForthWord).word))
            comma(Instruction.Jump(doconst))
            comma(0)
    }


    val enter = dictionary.here().also {
        dictionary.add(Instruction.FunCall {
            returnStack.push(ip)
            ip = w + 1
            eip = next
        })
    }

    val exit = createHeader("EXIT").also {
        dictionary.add(Instruction.FunCall {
            ip = returnStack.pop() as Int
            eip = next
        })
        lastDefinition = it
    }

    val eoln = defWord("") { ip = returnStack.pop() as Int }


    /*
    header:
    - link
    - precedence
    - name
     */
    private fun defWord(name: String, action: Instruction, precedence: Boolean): Int {
        createHeader(name, precedence)
        lastDefinition = dictionary.here()
        dictionary.add(action)
        return lastDefinition
    }

    fun createHeader(name: String, precedence: Boolean = false): Int {
        dictionary.add(lastDefinition)
        dictionary.add(precedence)
        dictionary.add(name)
        return dictionary.here()
    }

    fun definitionLink(address: Int): Int = address - 3
    fun definitionName(address: Int): Int = address - 1
    fun definitionPrecedence(address: Int): Int = address - 2
    fun definitionCodeField(address: Int): Int = address

    fun defWord(name: String, action: EKForth.() -> Unit) =
        defWord(
            name = name,
            action = Instruction.FunCall(action),
            precedence = false
        ).also {
            dictionary.add(Instruction.Jump(next))
        }

    fun findWord(name: String): Int? {
        var def = lastDefinition
        while (def >= 0) {
            if (name.equals(dictionary[definitionName(def)] as String, ignoreCase = true)) return definitionCodeField(def)
            def = dictionary[definitionLink(def)] as Int
        }
        return null
    }

    var output: (Any?) -> Unit = ::print

    val dot = defWord(".") {
        output.invoke("${stack.pop()} ")
    }

    val docon = defWord("DOCON") {
        stack.push(dictionary[pfa])
    }

    // ;C EXECUTE   i*x xt -- j*x   execute Forth word
    val execute = createHeader("EXECUTE").also {
        dictionary.add(Instruction.FunCall {
            w = stack.pop() as Int;
            pfa = w + 1
            //++ ip
            eip = w
        })
        lastDefinition = it
    }

    val kaccept = defWord("KACCEPT") {
            save()
            end = true
    }

    fun defColon(name: String, vararg tokens: Any) = defWord(name, Instruction.Jump(enter), precedence = false).also {
        tokens.forEach { xt -> dictionary.add(xt) }
        dictionary.add(exit)
    }

    fun colonDefinition(name: String, precedence: Boolean = false, definition: () -> Unit) = createHeader(name, precedence).also {
        comma(Instruction.Jump(enter))
        definition.invoke()
        comma(exit)
        lastDefinition = it
    }

    val bye = defWord("BYE", Instruction.End, false)

    val onStack = defWord("ONSTACK") {
        stack.push(dictionary[ip++])
    }

    val print = defWord("PRINT") {
        output.invoke(dictionary[ip++])
    }

    val branch = defWord("BRANCH") {
        ip = dictionary[ip] as Int
    }

    val qbranch = defWord("?BRANCH") {
        if (stack.pop() == 0) {
            ip = dictionary[ip] as Int
        } else {
            ++ ip
        }
    }

    fun kWord() = input.word()
    fun kDelimitedWord(delimiter: Char) = input.word(delimiter)

    val kWord = defWord("KWORD") {
        stack.push(kWord())
    }

    val kDelimitedWord = defWord("KDELIMITEDWORD") {
        stack.push(kDelimitedWord(stack.pop() as Char))
    }

    fun kFind(token: Any?): Pair<Any?, Int> {
        if (token is ForthWord) {
            val definition = findWord(token.word) ?: throw Exception("$token not found")
            val isImmediate = if (dictionary[definitionPrecedence(definition)] as Boolean) 1 else -1
            val xt = definitionCodeField(definition)
            return Pair(xt, isImmediate)
        } else {
            return Pair(token, 0)
        }
    }

    val kFind = defWord("KFIND") {
            val token = stack.pop()
            val (res, isImmediate) = kFind(token)
            stack.push(res)
            stack.push(isImmediate)
    }

    private fun begin() {
        stack.push(dictionary.here())
    }

    private fun again() {
        comma(branch)
        comma(stack.pop())
    }

    val dup = defWord("DUP") { stack.push(stack.peek()) }
    val qdup = defWord("?DUP") {
        val n = stack.peek()
        if (n != 0) stack.push(n)
    }
    val drop = defWord("DROP") { stack.pop() }

    val swap = defWord("SWAP") {
        val x = stack.pop()
        val y = stack.pop()
        stack.push(x)
        stack.push(y)
    }

    val over = defWord("OVER") {
        val x = stack.pop()
        val y = stack.peek()
        stack.push(x)
        stack.push(y)
    }

    val nip = defWord("NIP") {
        val x = stack.pop()
        stack.pop()
        stack.push(x)
    }

    val twoDup = defWord("2DUP") {
        val x = stack.pop()
        val y = stack.peek()
        stack.push(x)
        stack.push(y)
        stack.push(x)
    }

    val comma = defWord(",") { dictionary.add(stack.pop()) }
    val compilecomma = defWord("COMPILE,") { dictionary.add(stack.pop()) }
    val compileLiteral = defWord("COMPILE-LITERAL") {
        val literal = stack.pop()
        if (literal is ForthWord) error("Cannot inline ForthWord")
        dictionary.add(onStack)
        dictionary.add(literal)
    }

    private fun comma(obj: Any?): Int = dictionary.add(obj)

    val zerolessthan = defWord("0<") {
        val n = stack.pop()
        val flag = if (n is Int) { n > 0 } else { n as Double > 0 }
        stack.push(if (flag) -1 else 0)
    }

    val zerogreaterthan = defWord("0>") {
        val n = stack.pop()
        val flag = if (n is Int) { n < 0 } else { (n as Double) < 0 }
        stack.push(if (flag) -1 else 0)
    }

    val zeroequal = defWord("0=") {
        stack.push(if (stack.pop() == 0) -1 else 0)
    }

    val rBracket = defWord("]") {
        dictionary[state + 1] = -1
    }

    val lBracket = defWord("[") {
        dictionary[state + 1] = 0
    }

    val semicolon = defWord(";") {
            comma(exit)
            lastDefinition = stack.pop() as Int
            dictionary[state + 1] = 0
    }.also {
        dictionary[definitionPrecedence(it)] = true
    }

    val immediate = defWord("IMMEDIATE") {
        dictionary[definitionPrecedence(lastDefinition)] = true
    }

    val colon = defWord(":") {
        stack.push(createHeader((kWord() as ForthWord).word))
        dictionary.add(Instruction.Jump(enter))
        dictionary[state + 1] = -1
    }

    private fun fif() {
        comma(qbranch)
        stack.push(dictionary.here())
        comma(0)
    }

    private fun felse() {
        dictionary[stack.pop() as Int] = dictionary.here() + 2
        comma(branch)
        stack.push(dictionary.here())
        comma(0)
    }

    fun fthen() {
        dictionary[stack.pop() as Int] = dictionary.here()
    }

    val create = defWord("CREATE") {
        val word = kWord()
        if (word !is ForthWord) error("Cannot create definition for $word")
        if (word.word.isBlank()) error("Cannot create definition for a blank word")
        last = createHeader(word.word, precedence = false)
        dictionary.add(Instruction.Jump(docreate))
    }

    /*
    https://www.bradrodriguez.com/papers/moving3.htm
    (;CODE) is part of the word CONSTANT, so it executes when CONSTANT executes (Sequence 2). It performs the following actions:

a. It gets the address of the machine code that immediately follows. This is done by popping IP from the Forth Return Stack.

b. It puts that address into the Code Field of the word just defined by CREATE. The Forth word LAST (sometimes LATEST) gets the address of that word.

c. It does the action of EXIT (a.k.a. ;S) so that the Forth inner interpreter doesn't try to execute the machine code that follows as part of the Forth thread. This is the high-level "subroutine return" which ends a Forth thread.

    : (;CODE)
        R>                  \ pops the adrs of the machine code
        LAST @ NAME>        \ gets the CFA of the latest word
        !                   \ stores the code address in the
        ;                   \   Code Field
        =====================
        For DTC and STC, the action of ;CODE and (;CODE) is identical to ITC, with one important exception: instead of holding an address, the Code Field holds a JUMP or CALL instruction.
     */

    val parSemicolonCode = defWord("(;CODE)") {
        dictionary[last] = Instruction.Jump(ip)
        // interpretation state
        dictionary[state + 1] = 0
        lastDefinition = last // REVEAL
        // exit
        ip = returnStack.pop() as Int
        eip = next
    }
            /*
        .also {
        dictionary[definitionPrecedence(it)] = true
    }

             */


    /*
    https://www.bradrodriguez.com/papers/moving3.htm
    ;CODE is executed during Sequence 1, when CONSTANT is compiled. This is an example of a Forth IMMEDIATE word -- a word executed during the Forth compilation. ;CODE does three things:

a. it compiles the Forth word (;CODE) into CONSTANT,
b. it turns off the Forth compiler, and
c. it turns on the Forth assembler.
     */

    var last = -1

    val dodoes = enter

    val does = defWord("DOES>") {
        comma(parSemicolonCode)
        comma(Instruction.FunCall {
            /*
            right now:
            eip: the first cell of thread following (;code)
            ip: somewhere e. g. in the interpreter, which is fine
            w: CFA of the definition, w + 1 is the PFA of the definition

            we want:
            ip where it is
            w: before the thread following (:code)
            eip at dodoes = enter
             */
            stack.push(w + 1)
            w = eip - 1
            eip = dodoes
        })
    }.also { dictionary[definitionPrecedence(it)] = true }

    val clearReturnStack = defWord("CLEAR-RETURN-STACK") {
        returnStack.clear()
    }

    val printOK = defWord(".OK") {
        output.invoke("  ok ")
    }

    val cr = defWord("CR") {
        output.invoke("\n")
    }

    val fetch = defWord("@") {
        val value = when (val cell = stack.pop()) {
            is MemoryCell -> cell.getter.invoke()
            is Int -> dictionary[cell]
            else -> throw Exception("Invalid cell $cell")
        }
        stack.push(value)
    }

    val store = defWord("!") {
        val cell = stack.pop()
        val value = stack.pop()
        when (cell) {
            is MemoryCell -> cell.setter.invoke(value)
            is Int -> dictionary[cell] = value
            else -> throw Exception("Invalid cell $cell")
        }
    }

    val interpret = colonDefinition("INTERPRET") {
        begin()
        comma(kWord)
        comma(kFind)
        comma(qdup)
        fif()
        comma(state)
        comma(fetch)
        fif()
        comma(zerolessthan)
        fif()
        comma(execute)
        felse()
        comma(compilecomma)
        fthen()
        felse()
        comma(drop)
        comma(execute)
        fthen()
        felse()
        comma(state)
        comma(fetch)
        fif()
        comma(compileLiteral)
        fthen()
        //comma(wordNotFoundException)
        fthen()
        again()
    }

    val quit = colonDefinition("QUIT") {
        begin()
        comma(clearReturnStack)
        comma(lBracket)
        comma(kaccept)
        comma(interpret)
        comma(printOK)
        comma(cr)
        again()
        comma(bye)
    }

    val start = dictionary.here().also {
        dictionary.add(quit)
        dictionary.add(bye)
    }

    private val initPointers = Pointers(next, start, 0, 0)
    private var pointers = initPointers

    fun init() {
        eip = pointers.eip
        ip = pointers.ip
        w = pointers.w
        pfa = pointers.pfa
    }

    fun save() {
        pointers = Pointers(eip, ip, w, pfa)
    }

    fun mainLoop() {
        while (!end) {
            val error = (dictionary[eip] as Instruction).perform(this)
            if (error != null) {
                output.invoke("$error\n")
                pointers = initPointers
                run()
            }
        }
    }

    fun run() {
        if (eip >= 0 && dictionary[eip] == Instruction.End) return
        init()
        end = false
        mainLoop()
    }

    val dotS = defWord(".S") {
        output.invoke(stack.stack.map { it.toString() }.joinToString(" "))
    }

    val mul = defWord("*") {
        val x = stack.pop()
        val y = stack.pop()
        val result = if (x is Int) {
            if (y is Int) {
                x * y
            } else {
                x * (y as Double)
            }
        } else {
            (x as Double) * (y as Double)
        }
        stack.push(result)
    }

    val add = defWord("+") {
        val x = stack.pop()
        val y = stack.pop()
        val result = if (x is Int) {
            if (y is Int) {
                x + y
            } else {
                x + (y as Double)
            }
        } else {
            (x as Double) + (y as Double)
        }
        stack.push(result)
    }

    val sub = defWord("-") {
        val y = stack.pop()
        val x = stack.pop()
        val result = if (x is Int) {
            if (y is Int) {
                x - y
            } else {
                x - (y as Double)
            }
        } else {
            (x as Double) - (y as Double)
        }
        stack.push(result)
    }

    val div = defWord("/") {
        val y = stack.pop()
        val x = stack.pop()
        val result = if (x is Int) {
            if (y is Int) {
                x / y
            } else {
                x / (y as Double)
            }
        } else {
            (x as Double) / (y as Double)
        }
        stack.push(result)
    }

    val less = defWord("<") {
        val y = stack.pop()
        val x = stack.pop()
        val result = if (x is Int) {
            if (y is Int) {
                if (x < y) -1 else 0
            } else {
                if (x < y as Double) -1 else 0
            }
        } else {
            if ((x as Double) < (y as Double)) -1 else 0
        }
        stack.push(result)
    }

    val more = defWord(">") {
        val y = stack.pop()
        val x = stack.pop()
        val result = if (x is Int) {
            if (y is Int) {
                if (x > y) -1 else 0
            } else {
                if (x > y as Double) -1 else 0
            }
        } else {
            if ((x as Double) > (y as Double)) -1 else 0
        }
        stack.push(result)
    }

    val equal = defWord("=") {
        val y = stack.pop()
        val x = stack.pop()
        val result = if (x is Int) {
            if (y is Int) {
                if (x == y) -1 else 0
            } else {
                if (x == y as Double) -1 else 0
            }
        } else {
            if ((x as Double) == (y as Double)) -1 else 0
        }
        stack.push(result)
    }

    val negate = defWord("NEGATE") {
        val x = stack.pop()
        val result = if (x is Int) {
            - x
        } else {
            - (x as Double)
        }
        stack.push(result)
    }

    val q = colonDefinition("?") {
        comma(fetch)
        comma(dot)
    }

    val dotDictionary = defWord(".DICTIONARY") { output.invoke(dictionary) }

    val here = defWord("HERE") {
        stack.push(dictionary.here())
    }

    val oneplus = defWord("1+") {
        val n = stack.pop()
        when (n) {
            is Int -> stack.push(n + 1)
            is Double -> stack.push(n + 1.0)
            else -> error("Cannot add 1 to $n")
        }
    }

    val oneminus = defWord("1-") {
        val n = stack.pop()
        when (n) {
            is Int -> stack.push(n - 1)
            is Double -> stack.push(n - 1.0)
            else -> error("Cannot subtract 1 from $n")
        }
    }

    val rfrom = defWord("R>") {
        stack.push(returnStack.pop())
    }

    val toR = defWord(">R") {
        returnStack.push(stack.pop())
    }

    val twoToR = defWord("2>R") {
        val x = stack.pop()
        val y = stack.pop()
        returnStack.push(y)
        returnStack.push(x)
    }

    val rAt = defWord("R@") {
        stack.push(returnStack.peek())
    }

    val postpone = defWord("POSTPONE") {
        val (xt, isImmediate) = kFind(kWord())
        if (isImmediate < 0) {
            comma(onStack)
            comma(xt)
            comma(comma)
        } else if (isImmediate > 0) {
            comma(xt)
        }
    }.also {
        dictionary[definitionPrecedence(it)] = true
    }

    fun dotQuote() {
        comma(print)
        val word = kDelimitedWord('"')
        comma(if (word is ForthWord) word.word else word.toString())
    }

    val dotQuote = defWord(".\"") {
        dotQuote()
    }.also {
        dictionary[definitionPrecedence(it)] = true
    }

    fun paren() {
        kDelimitedWord(')')
    }

    val paren = defWord("(") {
        paren()
    }.also {
        dictionary[definitionPrecedence(it)] = true
    }

    val parLoop = defWord("(LOOP)") {
        val index = (returnStack.pop() as Int) + 1
        val limit = returnStack.peek() as Int
        val start = dictionary[ip] as Int
        if (index < limit) {
            returnStack.push(index)
            ip = start
        } else {
            returnStack.pop()
            ++ ip
        }
    }

    val parPlusLoop = defWord("(+LOOP)") {
        val increment = stack.pop() as Int
        val oldIndex = returnStack.pop() as Int
        val newIndex = oldIndex + increment
        val limit = returnStack.peek() as Int
        val start = dictionary[ip] as Int
        if ((oldIndex >= limit && newIndex >= limit) || (oldIndex <= limit - 1 && newIndex <= limit - 1)) {
            returnStack.push(newIndex)
            ip = start
        } else {
            returnStack.pop()
            ++ ip
        }
    }

    val csRoll = defWord("CS-ROLL") {
        stack.roll(stack.pop() as Int)
    }

    private val leaveAddresses = mutableListOf<MutableList<Int>>()

    val leave = defWord("LEAVE") {
        comma(rfrom)
        comma(drop)
        comma(rfrom)
        comma(drop)
        comma(branch)
        leaveAddresses.last().add(dictionary.here())
        comma(0)
    }.also {
        dictionary[definitionPrecedence(it)] = true
    }

    val startLeave = defWord("(STARTLEAVE)") {
        leaveAddresses.add(mutableListOf())
    }

    val endLeave = defWord("(ENDLEAVE)") {
        val addresses = leaveAddresses.removeLast()
        val here = dictionary.here()
        for (address in addresses) {
            dictionary[address] = here
        }
    }

    init {
        accept("1 2 + .")
        accept("""
            : IF   POSTPONE ?BRANCH  HERE  0 , ; IMMEDIATE
            : ELSE   HERE 2 + SWAP !  POSTPONE BRANCH  HERE  0 , ; IMMEDIATE
            : THEN   HERE SWAP ! ;  IMMEDIATE
            : DO  POSTPONE 2>R  (STARTLEAVE)  HERE ; IMMEDIATE
            : LOOP  POSTPONE (LOOP)  , (ENDLEAVE) ; IMMEDIATE
            : +LOOP  POSTPONE (+LOOP)  , (ENDLEAVE) ; IMMEDIATE
            : I POSTPONE R@ ; IMMEDIATE
            : BEGIN   HERE ; IMMEDIATE
            : AGAIN   POSTPONE BRANCH , ; IMMEDIATE
            : UNTIL   POSTPONE ?BRANCH , ; IMMEDIATE
            : REPEAT  POSTPONE AGAIN ; IMMEDIATE
            : REPEAT  POSTPONE AGAIN  POSTPONE THEN ; IMMEDIATE
            : WHILE ( dest -- orig dest )
               POSTPONE IF 1 CS-ROLL
            ; IMMEDIATE
        """.trimIndent())
        run()
    }

    fun isDone(): Boolean = (dictionary[eip] == Instruction.End)


}

data class ForthWord(val word: String)

val mainFn = ::main1
//val mainFn = ::mainServer
//val mainFn = ::forth
//val mainFn = { remoteForth(12345) }

fun main() {
    mainFn.invoke()
}

fun mainServer() {
    data class C(var a: Int, var b: Int)
    val c = C(1, 2)
    Forth.defConstant("c") { c }
    Forth.defVariable("c.a",
    getter = { c.a },
    setter = { c.a = it as Int }
    )
    Forth.script(": square   dup * ;")
    Forth.onSocket(12345)
}

fun forth() {
    EKForth().run {
        output = { print(it) }
        while (true) {
            val line = readLine() ?: break
            accept(line)
        }
    }
}

fun main2() {
    EKForth().run {
        //println(dictionary)
        //output = { print(it) }
        //accept("")
    }
}

fun main1() {
    EKForth().run {
        output = { print(it) }

        accept("""
            \.DICTIONARY
            \: hello  ." hello world " here . ;
            \cr hello
            \: ?FULL   12 = IF ." It's full " THEN ;
            \10 ?FULL
            \12 ?FULL
            \: ?FULL2  cr  dup . ." : " 12 = IF ." plenum" else ." nondum plenum" THEN ;
            \10 ?FULL2
            \12 ?FULL2
            : DECADE  10 0 DO  I .  LOOP ;
            : PENTAJUMPS  50 0 DO  I .  5 +LOOP ;
            : FALLING  -10 0 DO  I .  -1 +LOOP ;
            : INC-COUNT  DO  I . DUP +LOOP  DROP ;
            CR DECADE
            CR PENTAJUMPS
            CR FALLING
            CR 1 5 0 INC-COUNT
            CR 2 5 0 INC-COUNT
            CR -3 -10 10 INC-COUNT
            : foo  ( n -- )
               begin
                  cr ." begin" cr
               dup 10 < while
                  dup . 1+
               repeat
               drop
               cr ." end"
            ;
            0 foo
            
            : FACTORIAL ( +n1 -- +n2 )
               DUP 2 < IF DROP 1 EXIT THEN
               DUP
               BEGIN DUP 2 > WHILE
               1- SWAP OVER * SWAP
               REPEAT DROP
            ;
            cr 5 factorial .
            
            : GD5 123 SWAP 0 DO 
         I 4 > IF DROP 234 LEAVE THEN 
       LOOP ;
       cr .dictionary
            cr .s
       cr 1 GD5 .
       cr 5 GD5 . 
       cr 6 GD5 . 
        """.trimIndent())
        /*

        accept("""
            : DECADE   10 0 DO  I .  LOOP ;
            DECADE
        """.trimIndent())
         */
    }
}

fun remoteForth(port: Int) = ForthIOServer(EKForth(), port)

class ForthIOServer(val forth: EKForth, port: Int, backlog: Int = 5) : Closeable {

    private val oldOutput = forth.output
    private val sock = ServerSocket(port, backlog)
    private var thread: Thread = thread {
        while (!Thread.currentThread().isInterrupted) {
            sock.accept().use { client ->
                val reader = client.getInputStream().bufferedReader()
                val writer = client.getOutputStream().bufferedWriter()
                forth.output = { writer.write("$it"); writer.flush() }
                while (true) {
                    val input = reader.readLine() ?: break
                    try {
                        forth.accept(input)
                    } catch (e: IOException) {
                        break
                    }
                }
            }
            forth.output = oldOutput
        }
    }

    override fun close() {
        forth.output = oldOutput
        sock.close()
        thread.interrupt()
    }
}

object Forth {
    private var forth: EKForth? = null
    private val preamble = mutableListOf<(EKForth) -> Unit>()

    fun addToPreamble(action: EKForth.() -> Unit) {
        preamble.add(action)
        forth?.let { action.invoke(it) }
    }

    fun script(script: String) {
        addToPreamble { accept(script) }
    }

    fun defWord(name: String, action: () -> Unit) {
        addToPreamble { defWord(name, action) }
    }

    fun defConstant(name: String, value: () -> Any?) {
        addToPreamble { defWord(name) { stack.push(value.invoke()) } }
    }

    fun defVariable(name: String, getter: () -> Any?, setter: (Any?) -> Unit) {
        val memoryCell = MemoryCell(getter, setter)
        addToPreamble { defWord(name) { stack.push(memoryCell) } }
    }

    fun accept(input: String) {
        if (forth == null) setupForth()
        forth?.let {
            it.accept(input)
            if (it.isDone()) forth = null
        }
    }

    var output: (String) -> Unit = {}
        set(value) {
            field = value
            forth?.let { it.output = { output("$it") } }
        }

    private fun setupForth() {
        forth = EKForth().apply {
            output = {}
            preamble.forEach { it.invoke(this) }
        }
        output = output
    }

    private var sock: ServerSocket? = null
    private var io: SocketIO? = null

    fun onSocket(port: Int, backlog: Int = 5) {
        val ioUser = object : SocketIOUser {
            override fun input(input: String) {
                accept(input)
            }

            override fun onNewWriter(writer: (String) -> Unit) {
                output = writer
            }
        }
        sock = ServerSocket(port, backlog)
        sock?.let { io = SocketIO(it, ioUser) }
    }

    fun close() {
        sock?.close()
        io?.close()
        forth = null
    }
}

interface SocketIOUser {
    fun input(input: String) {}
    fun onNewWriter(writer: (String) -> Unit) {}
}

class SocketIO(val sock: ServerSocket, val user: SocketIOUser) : Closeable {
    private var thread: Thread = thread {
        while (!Thread.currentThread().isInterrupted) {
            sock.accept().use { client ->
                val reader = client.getInputStream().bufferedReader()
                val writer = client.getOutputStream().bufferedWriter()
                user.onNewWriter { writer.write(it); writer.flush() }
                while (true) {
                    val input = reader.readLine() ?: break
                    try {
                        user.input(input)
                    } catch (e: IOException) {
                        break
                    }
                }
            }
        }
    }

    override fun close() {
        thread.interrupt()
    }
}
